﻿using System;
using System.IO;
using System.IO.Packaging;
using System.Reflection;
using System.Xml;
using System.Xml.Xsl;
using TShooter.TfsWord.Resources;
using TShooter.TfsWord.Templates.Generator.Properties;

namespace TShooter.TfsWord.Templates.Generator
{
    public class TemplateGenerator
    {
        private const int BUFFER_SIZE = 1024;
        private const string BUILTIN_TEMPLATE_RESOURCE_PATH = @"Resources\TfsWordTemplate.docx";
        private const string OOXML_DOCUMENT_URI_STRING = "word/document.xml";
        private const string OOXML_WORD_DOCUMENT_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml";
        private const string BUILTIN_TRANSFORM_RESOURCE_PATH = @"Resources\TemplateGenerator.xslt";

        private static readonly Uri OOXML_DOCUMENT_URI = new Uri(OOXML_DOCUMENT_URI_STRING);

        private readonly XmlReader _Witd;
        private readonly string _DocxFileName;
        private readonly XslCompiledTransform _Transformer;

        public TemplateGenerator(XmlReader witd)
        {
            if (witd == null)
            {
                throw new ArgumentNullException("witd");
            }

            _Witd = witd;
        }

        public TemplateGenerator(XmlReader witd, string docxFileName)
        {
            if (witd == null)
            {
                throw new ArgumentNullException("witd");
            }
            if (docxFileName == null)
            {
                throw new ArgumentNullException("docxFileName");
            }
            if (!File.Exists(docxFileName))
            {
                throw new ArgumentException(string.Format(Strings.FileNotFound, docxFileName));
            }

            _Witd = witd;
            _DocxFileName = docxFileName;
            _Transformer = GetDefaultTransform();
        }

        public TemplateGenerator(XmlReader witd, string docxFileName, XmlReader transformer)
        {
            if (witd == null)
            {
                throw new ArgumentNullException("witd");
            }
            if (docxFileName == null)
            {
                throw new ArgumentNullException("docxFileName");
            }
            if (!File.Exists(docxFileName))
            {
                throw new ArgumentException(string.Format(Strings.FileNotFound, docxFileName));
            }
            if (transformer == null)
            {
                throw new ArgumentNullException("transformer");
            }

            _Witd = witd;
            _DocxFileName = docxFileName;
            _Transformer = LoadTransform(transformer);
        }

        public TemplateGenerator(string witd)
            : this(new XmlTextReader(new StringReader(witd)))
        {
        }

        public TemplateGenerator(string witd, string docxFileName)
            : this(new XmlTextReader(new StringReader(witd)), docxFileName)
        {
        }

        public TemplateGenerator(string witd, string docxFileName, string transformer)
            : this(new XmlTextReader(new StringReader(witd)), docxFileName, new XmlTextReader(new StringReader(transformer)))
        {
        }

        private static XslCompiledTransform GetDefaultTransform()
        {
            var assembly = Assembly.GetExecutingAssembly();

            XslCompiledTransform transform;

            using (var resourceStream = assembly.GetManifestResourceStream(BUILTIN_TRANSFORM_RESOURCE_PATH))
            {
                if (resourceStream == null)
                {
                    throw new IOException(string.Format(Strings.ResourceNotFound, BUILTIN_TRANSFORM_RESOURCE_PATH, assembly.GetName()));
                }

                var reader = new XmlTextReader(resourceStream);
                transform = LoadTransform(reader);
            }

            return transform;
        }

        private static XslCompiledTransform LoadTransform(XmlReader reader)
        {
            var transform = new XslCompiledTransform();

            using (reader)
            {
                transform.Load(reader);
            }

            return transform;
        }

        public void Generate(string outputFileName)
        {
            var workingFile = CreateWorkingFile();

            using (var pkg = Package.Open(workingFile))
            {
                var packagePart = !pkg.PartExists(OOXML_DOCUMENT_URI) ?
                    pkg.CreatePart(OOXML_DOCUMENT_URI, OOXML_WORD_DOCUMENT_CONTENT_TYPE) :
                    pkg.GetPart(OOXML_DOCUMENT_URI);

                var xml = GenerateDocumentXml();

                using (var sw = new StreamWriter(packagePart.GetStream(FileMode.OpenOrCreate, FileAccess.ReadWrite)))
                {
                    sw.Write(xml);
                }
            }

            File.Move(workingFile, outputFileName);
        }

        private string GenerateDocumentXml()
        {
            var sw = new StringWriter();

            using (var xtw = new XmlTextWriter(sw))
            {
                _Transformer.Transform(_Witd, xtw);
            }

            return sw.ToString();
        }

        private string CreateWorkingFile()
        {
            var tempFileName = Path.GetTempFileName();

            if (_DocxFileName != null)
            {
                File.Copy(_DocxFileName, tempFileName);
            }
            else
            {
                ExtractBuiltInTemplate(tempFileName);
            }

            return tempFileName;
        }

        private static void ExtractBuiltInTemplate(string tempFileName)
        {
            var assembly = Assembly.GetExecutingAssembly();

            using (var resourceStream = assembly.GetManifestResourceStream(BUILTIN_TEMPLATE_RESOURCE_PATH))
            {
                if (resourceStream == null)
                {
                    throw new IOException(string.Format(Strings.ResourceNotFound, BUILTIN_TEMPLATE_RESOURCE_PATH, assembly.GetName()));
                }
                using (var outputStream = File.Create(tempFileName))
                {
                    var buf = new byte[BUFFER_SIZE];
                    int i;

                    while ((i = resourceStream.Read(buf, 0, BUFFER_SIZE)) > 0)
                    {
                        outputStream.Write(buf, 0, i);
                    }
                }
            }
        }
    }
}
